


(function() {
    let {interfaces: Ci, utils: Cu, results: Cr, classes: Cc} = Components;
    function require(url) {
        let scope = {};
        Cu.import(url, scope);
        return scope;
    }
    let {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
    let {StrUtils} = require("chrome://jsm/content/utils.js");

    let connector = {
        messageCount: 0,
        postMessage(topic, data) {
            data.messageCount = ++this.messageCount;
	    sendAsyncMessage("imacros:"+topic, data);
        }
    };


    function FrameScriptRecorder() {
        this.onChangeEvent = this.onChange.bind(this);
        this.onClickEvent = this.onClick.bind(this);
        this.onMouseDownEvent = this.onMouseDown.bind(this);
        this.onMouseUpEvent = this.onMouseUp.bind(this);
        this.onMouseMoveEvent = this.onMouseMove.bind(this);
        this.onMouseClickEvent = this.onMouseClick.bind(this);
        this.onDblClickEvent = this.onMouseDblClick.bind(this);
        this.onKeyPressEvent2 = this.onKeyPress2.bind(this);
        this.onKeyPressEvent = this.onKeyPress.bind(this);
        this.onKeyDownEvent = this.onKeyDown.bind(this);

        addMessageListener("imacros:start-recording", function(msg) {
	    recorder.start(msg.data);
        });
        addMessageListener("imacros:stop-recording", function(msg) {
	    recorder.stop();
        });
    }

    FrameScriptRecorder.prototype.addDOMEventsListeners = function(win) {
        if (!win)
            return;
        if (this.recordMode == "events") {
            win.addEventListener("mousedown", this.onMouseDownEvent, true);
            win.addEventListener("mouseup", this.onMouseUpEvent, true);
            win.addEventListener("click", this.onMouseClickEvent, true);
            win.addEventListener("dblclick", this.onDblClickEvent, true);
            win.addEventListener("keypress", this.onKeyPressEvent2, true);
        } else {
            win.addEventListener("click", this.onClickEvent, true);
            win.addEventListener("change", this.onChangeEvent, true);
            win.addEventListener("keydown", this.onKeyDownEvent, true);
            win.addEventListener("keypress", this.onKeyPressEvent, true);
        }
        let self = this;
        win.addEventListener("unload", function listener () {
            self.removeDOMEventsListeners(win);
            win.removeEventListener("unload", listener);
        });
    };

    FrameScriptRecorder.prototype.removeDOMEventsListeners = function(win) {
        if (!win)
            return;
        if (this.recordMode == "events") {
            win.removeEventListener("mousedown", this.onMouseDownEvent, true);
            win.removeEventListener("mouseup", this.onMouseUpEvent, true);
            win.removeEventListener("click", this.onMouseClickEvent, true);
            win.removeEventListener("dblclick", this.onDblClickEvent, true);
            win.removeEventListener("keypress", this.onKeyPressEvent2, true);
        } else {
            win.removeEventListener("click", this.onClickEvent, true);
            win.removeEventListener("change", this.onChangeEvent, true);
            win.removeEventListener("keydown", this.onKeyDownEvent, true);
            win.removeEventListener("keypress", this.onKeyPressEvent, true);
        }
    };

    function for_each_frame(win, func) {
        if (!win)
            return;
        func(win);
        for (let i = 0; i < win.frames.length; i++) {
            for_each_frame(win.frames[i], func);
        }
    }

    FrameScriptRecorder.prototype.start = function(args) {
        let wn = docShell.QueryInterface(Ci.nsIWebNavigation);
        wn.sessionHistory.addSHistoryListener(this);
        this.recording = true;
        this.submitter = null;
        this.favorIds = args.favorId;
        this.recordMode = args.recordMode;
        this.currentFrame = content;
        for_each_frame(content, w => this.addDOMEventsListeners(w));
        Services.obs.addObserver(this, "document-element-inserted", false);
    };

    FrameScriptRecorder.prototype.stop = function() {
        let wn = docShell.QueryInterface(Ci.nsIWebNavigation);
        wn.sessionHistory.removeSHistoryListener(this);
        this.recording = false;
        this.currentFrame = content;
        this.submitter = null;
        for_each_frame(content, w => this.removeDOMEventsListeners(w));
        Services.obs.removeObserver(this, "document-element-inserted", false);
    };

    FrameScriptRecorder.prototype.observe = function(subject, topic, data) {
        
        
        this.addDOMEventsListeners(subject.defaultView);
    };

    
    
    
    FrameScriptRecorder.prototype.OnHistoryGoForward = function(uri) {
        return true;
    };
    FrameScriptRecorder.prototype.OnHistoryGotoIndex = function(i,u) {
        return true;
    };
    FrameScriptRecorder.prototype.OnHistoryNewEntry = function(uri) {};
    FrameScriptRecorder.prototype.OnHistoryPurge = function(num) {return true;};
    FrameScriptRecorder.prototype.OnHistoryGoBack = function(backURI) {
        this.saveAction("BACK"); return true;
    };
    FrameScriptRecorder.prototype.OnHistoryReload = function(uri, flags) {
        this.saveAction("REFRESH"); return true;
    };
    FrameScriptRecorder.prototype.QueryInterface =
        XPCOMUtils.generateQI(["nsISHistoryListener",
                               "nsISupportsWeakReference"]);
    
    FrameScriptRecorder.prototype.saveAction = function(str, extra) {
        connector.postMessage(
            "record-action", {action: str, extra: extra || null}
        );
    };

    FrameScriptRecorder.prototype.checkForFrameChange = function(elem) {
        
        
        function find_frame_no(win, f, obj) {
            if (win.top == f)         
                return 0;
            for (var i = 0; i < win.frames.length; i++) {
                obj.num++;
                if ( win.frames[i] == f) {
                    return obj.num;
                }
                var n = find_frame_no(win.frames[i], f, obj);
                if (n != -1)
                    return n;
            }
            return -1;
        }

        var win = elem.ownerDocument.defaultView; 
        if (!this.currentFrame || this.currentFrame.closed ||
            !this.currentFrame.document) {
            this.currentFrame = content;
        }
        
        if (this.currentFrame != win) {
            var nframe = 0, rec;
            this.currentFrame = win;
            if (win.frameElement && win.frameElement.name) {
                rec = "FRAME NAME=\""+win.frameElement.name+"\"";
            } else {
                nframe = find_frame_no(win.top, win, {num:0});
                rec = "FRAME F="+nframe.toString();
            } 
            this.saveAction(rec);
        }
    };

    const im_strre = "(?:\"(?:[^\"\\\\]|\\\\[0btnvfr\"\'\\\\])*\"|\\S*)";
    
    FrameScriptRecorder.prototype.parseAtts = function(str) {
        if (!str || str == "*")
            return null;
        var arr = str.split(new RegExp("&&(?=[-\\w]+:"+im_strre+")"));
        var parsed_atts = new Object(), at, val, m;
        const re = new RegExp("^([-\\w]+):("+im_strre+")$");
        for (var i = 0; i < arr.length; i++) {
            if (!(m = re.exec(arr[i])))
                throw new BadParameter("incorrect ATTR or FORM specifier: "
                                       +arr[i]);
            at = m[1].toLowerCase();
            if (at.length) {
                val = StrUtils.unwrap(m[2]);
                
                
                
                val = StrUtils.escapeTextContent(val);
                val = StrUtils.escapeREChars(val);
                val = val.replace(/\*/g, '(?:\n|.)*');
                
                val = val.replace(/ /g, "\\s+");
                parsed_atts[at] = "^\\s*"+val+"\\s*$";
            } else {
                parsed_atts[at] = "^$";
            }
        }
        for (var x in parsed_atts) 
            parsed_atts[x] = new RegExp(parsed_atts[x]);

        return parsed_atts;
    };


    FrameScriptRecorder.prototype.match = function(node, atts) {
        var match = true;

        for (var at in atts) {
            if (at == "txt") {
                var txt = StrUtils.escapeTextContent(node.textContent);
                if (!atts[at].exec(txt)) {
                    match = false; break;
                }
            } else {
                var atval = "", propval = "";
                
                if (at in node) {
                    propval = node[at];
                    
                    
                    
                    
                    
                    if (at == "type" && is_html5_input_type(propval))
                        propval = "text";
                } else if (at == "href" && "src" in node) {
                    
                    
                    propval = node.src;
                }
                
                if (node.hasAttribute(at)) {
                    atval = node.getAttribute(at);
                }
                
                if (!(!!atts[at].exec(propval) || !!atts[at].exec(atval))) {
                    match = false; break;
                }
            } 
        }
        return match;
    };


    FrameScriptRecorder.prototype.findPosition =
        function(element,atts,form_atts)
    {
        
        
        var xpath = "descendant-or-self::*[translate(local-name(),"+
            "'ABCDEFGHIJKLMNOPQRSTUVWXYZ',"+
            "'abcdefghijklmnopqrstuvwxyz')='"+
            element.tagName.toLowerCase()+"']";
        var doc = element.ownerDocument;
        var ctx = doc.documentElement;
        var nodes = new Array(), count = 0;
        
        try {
            var res = doc.evaluate(
                xpath, ctx, null,
		content.XPathResult.ORDERED_NODE_ITERATOR_TYPE, null
            );
            var node = null;
            while (node = res.iterateNext()) {
                nodes.push(node);
            }
        } catch (e) {
            Components.utils.reportError(e);
        }
        
        
        if (form_atts && form_atts["name"] &&
            form_atts["name"].exec("NoFormName"))
            form_atts = null;
        
        
        for (var i = 0; i < nodes.length; i++) {
            
            
            var match = atts ? this.match(nodes[i], atts) : true;
            
            if (match && form_atts && nodes[i].form)
                match = this.match(nodes[i].form, form_atts);
            if (match) 
                count++;
            if (nodes[i] == element)
                break;
        }

        return count;
    };


    FrameScriptRecorder.prototype.makeFormRecord = function(elem) {
        var form = "";
        if (elem.form) {
            if (elem.form.id && this.favorIds) {
                form = "ID:"+StrUtils.wrap(elem.form.id);
            } else {
                
                
                
                if (elem.form.hasAttribute('name')) {
                    form = "NAME:"+StrUtils.wrap(
                        elem.form.getAttribute('name')
                    );
                } else if (elem.form.action) {
                    var x;
                    if (!(x = elem.form.getAttribute("action")))
                        x = elem.form.action;
                    form = "ACTION:"+StrUtils.wrap(x);
                } else {
                    form = "NAME:NoFormName";
                }
            }
        }

        return form;
    };


    FrameScriptRecorder.prototype.makeAttrRecord = function (elem) {
        
        var truncate = function(s) {
            s = s.toString();
            if (s.length > 60) {
                s = s.substring(0, 60);
                s = s.replace(/(?:<|<\w{0,2}|<\w{2}>)+$/, "");
                s += "*";
            } 
            return s;
        };

        var attr = "";

        if ("input" == elem.tagName.toLowerCase()) {
            if (this.favorIds && elem.id) {
                attr = "ID:"+StrUtils.wrap(elem.id);
            } else {
                var arr = new Array();
                if (elem.name)
                    arr.push("NAME:"+StrUtils.wrap(elem.name));
                if (elem.src)
                    arr.push("SRC:"+StrUtils.wrap(elem.src));
                attr = arr.length ? arr.join("&&") : "*";
            }
        } else {
            var val = "";
            if (this.favorIds && elem.id) {
                val = "ID:"+StrUtils.wrap(elem.id);
            } else if (elem.href) {
                
                if (elem.textContent) {
                    val = "TXT:"+truncate(StrUtils.wrap(
                        StrUtils.escapeTextContent(elem.textContent)
                    ));
                } else {
                    val = "HREF:"+StrUtils.wrap(elem.href);
                }
            } else {
                if (elem.src) {
                    val = "SRC:"+StrUtils.wrap(elem.src);
                } else if (elem.name) {
                    val = "NAME:"+StrUtils.wrap(elem.name);
                } else if (elem.alt) {
                    val = "ALT:"+StrUtils.wrap(elem.alt);
                } else if (elem.textContent) {
                    val = "TXT:"+truncate(StrUtils.wrap(
                        StrUtils.escapeTextContent(elem.textContent)
                    ));
                }
            }

            if (!val) {  
                var x = elem.attributes, arr = new Array();
                for (var i = 0; i < x.length; i++) {
                    if (/^style$/i.test(x[i].name))
                        continue;
                    arr.push(x[i].name.toUpperCase()+":"+
                             StrUtils.wrap(x[i].value));
                }

                arr.push("TXT:"+truncate(StrUtils.wrap(
                    StrUtils.escapeTextContent(elem.textContent)
                )));

                val = arr.length ? arr.join("&&") : "*";
            }
            attr = val;
        }
        
        return attr;
    };


    let html5_input_types = new Set(
        ["color", "date", "datetime", "datetime-local",
         "email", "month", "number", "range", "search",
         "tel", "time", "url", "week"]
    );
    function is_html5_input_type(type) {
        return html5_input_types.has(type.toLowerCase());
    }


    function is_html5_text_input_type(type) {
        let t = type.toLowerCase();
        return t == "email" ||
            t == "search" ||
            t == "tel" ||
            t == "url";
    }


    FrameScriptRecorder.prototype.onChange = function(e) {
        if (!e.isTrusted)
            return;
        let elem = e.target;
        let tagName = elem.tagName;

        if (!/^(?:input|textarea|select)$/i.test(tagName) ||
            /^input$/i.test(tagName) &&
            !(is_html5_input_type(elem.type) ||
              /^(?:text|password|checkbox|file)$/i.test(elem.type))
           )
            return;

        this.checkForFrameChange(elem);
        let rec = "", pos = 0, tag_content = "", type = tagName;

        
        switch (tagName) {
        case "INPUT":
            type += ":"+elem.type.toUpperCase();
            if (is_html5_input_type(elem.type) ||
                /^(?:text|file)$/i.test(elem.type)) {
                tag_content = StrUtils.wrap(elem.value);
            } else if (elem.type == "password") {
                
                
                tag_content = StrUtils.wrap(elem.value);
            } else if (elem.type == "checkbox") {
                tag_content = elem.checked ? "YES" : "NO";
            } 
            break;
        case "SELECT":
            for(let i = 0; i < elem.length; i++) {
                var prefix, text;
                if(!elem[i].selected)
                    continue;
                
                if (elem[i].value) {
                    prefix = "%";
                    text = elem[i].value;
                } else {
                    prefix = "$";
                    text = escapeTextContent(elem[i].textContent);
                }
                if (!tag_content) 
                    tag_content = prefix + StrUtils.wrap(text);
                else
                    tag_content += ":" + prefix + StrUtils.wrap(text);
            }
            break;
        case "TEXTAREA":
            tag_content = StrUtils.wrap(elem.value);
            break;
        default:
            return;
        }


        let form = this.makeFormRecord(elem); 
        let attr = this.makeAttrRecord(elem); 
        let atts = this.parseAtts(attr);      

        
        if (/input/i.test(tagName)) { 
            if (!atts) atts = new Object();
            atts["type"] = new RegExp("^"+elem.type+"$");
        }
        
        var form_atts = form ? this.parseAtts(form) : null;

        if (!(pos = this.findPosition(elem, atts, form_atts))) {
            
            console.error("Can't find element position, atts="+
                          atts.toSource());
            return;
        }

        
        this.highlightElement(elem);

        
        rec = "TAG POS="+pos+" TYPE="+type;
        rec += form ? " FORM="+form : "";
        rec += " ATTR="+attr;
        rec += " CONTENT="+tag_content;
        this.saveAction(rec);

        
        
        
        if (this.submitter) {
            tagName = this.submitter.tagName.toUpperCase();
            type = tagName;
            if (tagName == "INPUT")
                type += ":"+this.submitter.type.toUpperCase();
            form = this.makeFormRecord(this.submitter);
            attr = this.makeAttrRecord(this.submitter);

            
            atts = this.parseAtts(attr);
            if (!atts) atts = new Object();
            atts["type"] = new RegExp("^"+this.submitter.type+"$");
            form_atts = form ? this.parseAtts(form) : null;
            pos = this.findPosition(this.submitter, atts, form_atts);
            if (!pos) {
                
                console.error("Can't find element position, atts="+
                              atts.toSource());
                return;
            }
            
            this.highlightElement(this.submitter);
            
            rec = "TAG POS="+pos+" TYPE="+type;
            rec += form ? " FORM="+form : "";
            rec += " ATTR="+attr;
            this.saveAction(rec);
            this.submitter = null;   
        }
    };



    FrameScriptRecorder.prototype.onKeyDown = function(e) {
        if (!e.isTrusted)
            return;
        
        let elem = e.target;
        let tagName = elem.tagName;

        if (tagName.toLowerCase() != "input" ||
            !(is_html5_text_input_type(elem.type) || 
              /^(?:text|password)$/i.test(elem.type)))
            return;

        if (e.keyCode != 13 && e.keyCode != 14)
            return;
        
        if (elem.form) {
            for (var i = 0; i < elem.form.elements.length; i++) {
                if (/submit/i.test(elem.form.elements[i].type)) {
                    
                    
                    this.submitter = elem.form.elements[i];
                    break;
                }
            }
        }
    };


    FrameScriptRecorder.prototype.onKeyPress = function(e) {
        if (!e.isTrusted)
            return;
        let elem = e.target;
        let tagName = elem.tagName;

        
	

        if (!/^(?:input|textarea)$/i.test(tagName))
            return;
        
        if (/^input$/i.test(tagName) &&
            !(is_html5_text_input_type(elem.type) || 
              /^(?:text|password)$/i.test(elem.type)))
            return;

        this.checkForFrameChange(elem);
        let val = e.charCode ? String.fromCharCode(e.charCode) : "";
        let rec = "", type = tagName , pos = 0, tag_content = "";

        
        switch (tagName) {
        case "INPUT":
            type += ":"+elem.type.toUpperCase();
            if (is_html5_text_input_type(elem.type) ||
                elem.type.toLowerCase() == "text") {
                tag_content = StrUtils.wrap(elem.value+val);
            } else if (elem.type == "password") {
                
                
                tag_content = StrUtils.wrap(elem.value+val);
            } 
            break;
            
        case "TEXTAREA":
            tag_content = StrUtils.wrap(elem.value+val);
            break;
        default:
            return;
        }

        let form = this.makeFormRecord(elem); 
        let attr = this.makeAttrRecord(elem); 
        let atts = this.parseAtts(attr);      

        
        if (/input/i.test(tagName)) { 
            if (!atts) atts = new Object();
            atts["type"] = new RegExp("^"+elem.type+"$");
        }
        
        var form_atts = form ? this.parseAtts(form) : null;

        if (!(pos = this.findPosition(elem, atts, form_atts))) {
            
            console.error("Can't find element position, atts="+
                          atts.toSource());
            return;
        }

        
        this.highlightElement(elem);
        
        
        rec = "TAG POS="+pos+" TYPE="+type;
        rec += form ? " FORM="+form : "";
        rec += " ATTR="+attr;
        rec += " CONTENT="+tag_content;
        this.saveAction(rec);

    };



    FrameScriptRecorder.prototype.onClick = function(e) {
        if (e.button != 0 || !e.isTrusted) 
            return;
        
        let elem = e.target;
        let tagName = elem.tagName.toUpperCase();

        if (/^(?:select|option|textarea|form|html|body)$/i.test(tagName))
            return;
        else if (/^input$/i.test(tagName) &&
                 !/^(?:button|submit|radio|image)$/i.test(elem.type))
            return;

        this.checkForFrameChange(elem);

        let rec = "", pos = 0, tag_content = "", type = tagName;

        if (tagName.toLowerCase() == "input")
            type += ":"+elem.type.toUpperCase();
        
        var form = this.makeFormRecord(elem);
        var attr = this.makeAttrRecord(elem);

        
        var atts = this.parseAtts(attr);
        
        if (/input/i.test(tagName)) { 
            if (!atts) atts = new Object();
            atts["type"] = new RegExp("^"+elem.type+"$");
        }

        var form_atts = form ? this.parseAtts(form) : null;
        if (!(pos = this.findPosition(elem, atts, form_atts))) {
            
            console.error("Can't find element position, atts="+atts.toSource());
            return;
        }

        
        this.highlightElement(elem);
        
        
        rec = "TAG";
        rec += " POS="+pos;
        rec += " TYPE="+type;
        rec += form ? " FORM="+form : "";
        rec += " ATTR="+attr;
        rec += tag_content ? " CONTENT="+tag_content : "";
        this.saveAction(rec);
    };


    FrameScriptRecorder.prototype.highlightElement = function(element) {
        var doc = element.ownerDocument;
        var hl_div = doc.createElement("div");
        hl_div.id = "imacros-highlight-div";
        hl_div.style.position = "absolute";
        hl_div.style.zIndex = 1000;
        hl_div.style.border = "1px solid #aaaaaa";
        hl_div.style.border = "1px solid blue";
        hl_div.style.borderRadius = "2px";
        var hl_img = doc.createElement("div");
        hl_img.style.display="block";
        hl_img.style.width = "24px";
        hl_img.style.height = "24px";
        hl_img.style.backgroundImage = "url('data:image/png;base64,"+
            "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"+
            "AAABJRU5ErkJggg==')";
        hl_div.appendChild(hl_img);
        doc.body.appendChild(hl_div);
        var rect = element.getBoundingClientRect();
        var scrollX = doc.defaultView.scrollX;
        var scrollY = doc.defaultView.scrollY;
        hl_div.style.left = Math.round(rect.left-1+scrollX)+"px";
        hl_div.style.top = Math.round(rect.top-1+scrollY)+"px";
        hl_div.style.width = Math.round(rect.width)+"px";
        hl_div.style.height = Math.round(rect.height)+"px";
        
        if (rect.top > 26) {
            hl_img.style.marginLeft = "4px";
            hl_img.style.marginTop = "-26px";
        } else if (rect.bottom+26 < doc.body.clientHeight) {
            hl_img.style.marginLeft = "4px";
            hl_img.style.marginBottom = "-26px";
        } else if (rect.left > 26) {
            hl_img.style.marginLeft = "-26px";
            hl_img.style.marginTop = "4px";
        } else if (rect.right+26 < doc.body.clientWidth) {
            hl_img.style.marginRight = "-26px";
            hl_img.style.marginTop = "4px";
        } else {
            hl_img.style.marginLeft = "0px";
            hl_img.style.marginTop = "0px";
        }

        doc.defaultView.setTimeout(function() {
            (hl_div.parentNode || hl_div.ownerDocument).
                removeChild(hl_div);
        }, 500);
    };



    
    
    
    FrameScriptRecorder.prototype.escapeIdForSelector = function(id) {
        
        

        
        id = id.replace(/([!"#$%&'()*+\.\/:;<=>?@\[\\\]^`{|}~])/g, '\\$1');
        
        id = id.replace(/^(\d)/, '\\3$1 ');
        
        
        
        id = id.replace(/^-([0-9-])/, '\\-$1');
        
        
        id = id.replace(/[\t\n\v\f\r]/g, function(s) {
            
            
            return "\\"+s.charCodeAt(0).toString()+' ';
        });

        return id;
    };

    FrameScriptRecorder.prototype.getSelectorForElement = function(el) {
        
        
        var selector = "", temp = el;
        while (temp.parentNode) {
            if (temp.id && this.favorId) {
                selector = "#"+
                    StrUtils.escapeLine(this.escapeIdForSelector(temp.id))+
                    (selector.length ? ">"+selector : "");
                return selector;
            }

            var siblings = temp.parentNode.childNodes, count = 0;
            for (var i = 0; i < siblings.length; i++) {
                if (siblings[i].nodeType != content.Node.ELEMENT_NODE)
                    continue;
                if (siblings[i] == temp)
                    break;
                if (siblings[i].tagName == temp.tagName)
                    count++;
            }

            if (count) {
                selector = temp.tagName+
                    ":nth-of-type("+(count+1)+")"+
                    (selector.length ? ">"+selector : "");
            } else {
                selector = temp.tagName+
                    (selector.length ? ">"+selector : "");
            }

            temp = temp.parentNode;
        }

        return selector;
    };

    FrameScriptRecorder.prototype.getModifiers = function(event) {
        var modifiers = [];
        if (event.ctrlKey)
            modifiers.push("ctrl");
        if (event.altKey)
            modifiers.push("alt");
        if (event.shiftKey)
            modifiers.push("shift");
        if (event.metaKey)
            modifiers.push("meta");

        return modifiers.join("|");
    };

    FrameScriptRecorder.prototype.onMouseDown = function(event) {
        if (!event.isTrusted)
            return;
        this.checkForFrameChange(event.target);
        let selector = this.getSelectorForElement(event.target);
        if (event.button == 0) {
            
            for_each_frame(
                content,
                w => w.addEventListener(
                    "mousemove", this.onMouseMoveEvent, false
                )
            );
        }
        let modifiers = this.getModifiers(event);
        this.saveAction(
            "EVENT TYPE=MOUSEDOWN SELECTOR=\""+selector+"\""+
                " BUTTON="+event.button+
                (modifiers.length ? " MODIFIERS=\""+modifiers+"\"" : "")
        );
    };


    FrameScriptRecorder.prototype.onMouseUp = function(event) {
        if (!event.isTrusted)
            return;
        if (event.button == 0) {
            for_each_frame(
                content,
                w => w.removeEventListener(
                    "mousemove", this.onMouseMoveEvent, false
                )
            );
        }
        this.checkForFrameChange(event.target);
        let selector = this.getSelectorForElement(event.target);
        this.saveAction(
            "EVENT TYPE=MOUSEUP POINT=\"("+event.pageX+","+event.pageY+")\""
            
        );
    };

    FrameScriptRecorder.prototype.onMouseMove = function(event) {
        var selector = this.getSelectorForElement(event.target);
        var modifiers = this.getModifiers(event);
        this.saveAction(
            "EVENT TYPE=MOUSEMOVE SELECTOR=\""+selector+"\""+
                " POINT=\"("+event.pageX+","+event.pageY+")\""+
                (modifiers.length ? " MODIFIERS=\""+modifiers+"\"" : ""),
            {pack_type: "mousemove", selector: selector,
             point: {x: event.pageX, y: event.pageY},
             modifiers: modifiers}
        );
    };

    FrameScriptRecorder.prototype.onMouseClick = function(event) {
        if (!event.isTrusted)
            return;
        let selector = this.getSelectorForElement(event.target);
        let modifiers = this.getModifiers(event);
        this.saveAction(
            "EVENT TYPE=CLICK SELECTOR=\""+selector+"\""+
                " BUTTON="+event.button+
                (modifiers.length ? " MODIFIERS=\""+modifiers+"\"" : ""),
            {pack_type: "click", selector: selector}
        );
        
        
    };

    FrameScriptRecorder.prototype.onMouseDblClick = function(event) {
        if (!event.isTrusted)
            return;
        let selector = this.getSelectorForElement(event.target);
        let modifiers = this.getModifiers(event);
        this.saveAction(
            "EVENT TYPE=DBLCLICK SELECTOR=\""+selector+"\""+
                " BUTTON="+event.button+
                (modifiers.length ? " MODIFIERS=\""+modifiers+"\"" : ""),
            {pack_type: "dblclick", selector: selector}
        );
    };

    FrameScriptRecorder.prototype.onKeyPress2 = function(event) {
        if (!event.isTrusted)
            return;
        this.checkForFrameChange(event.target);
        var selector = this.getSelectorForElement(event.target);
        var modifiers = this.getModifiers(event);
        var use_char = !!(event.which && event.charCode), char = "", key = 0;
        if (use_char)
            char = String.fromCharCode(event.which);
        else
            key = event.keyCode;
        
        var is_encryptable = event.target.type == "password" && use_char;
        this.saveAction(
            "EVENT TYPE=KEYPRESS SELECTOR=\""+selector+"\""+
                (use_char? " CHAR=\""+StrUtils.escapeLine(char)+"\"" :
                 (" KEY="+key+
                  (modifiers.length ? " MODIFIERS=\""+modifiers+"\"" : "")
                 )
                ),
            {pack_type: "keypress", selector: selector,
             encrypt: is_encryptable,
             use_char: use_char, char: char, key: key,
             modifiers: modifiers}
        );
        
        
    };

    let recorder = new FrameScriptRecorder();

}) ();
